Zvládněte ověřování OAuth2 ve FastAPI! Tento průvodce pokrývá tok heslem, implicitní tok, tok autorizačním kódem, obnovu tokenu a osvědčené bezpečnostní postupy pro vytváření robustních API.
Implementace OAuth2 ve FastAPI: Komplexní průvodce ověřovacími toky
V dnešní digitální krajině je zabezpečení vašich API prvořadé. OAuth2 (Open Authorization) se stal průmyslovým standardem pro delegovanou autorizaci, umožňující uživatelům udělit omezený přístup ke svým zdrojům bez sdílení svých přihlašovacích údajů. FastAPI, moderní, vysoce výkonný webový framework v Pythonu, usnadňuje implementaci ověřování OAuth2. Tento komplexní průvodce vás provede různými toky OAuth2 a ukáže, jak je integrovat do vaší aplikace FastAPI, čímž zajistíte, že vaše API zůstane bezpečné a přístupné.
Pochopení konceptů OAuth2
Než se ponoříme do kódu, ujasněme si základní koncepty OAuth2:
- Vlastník zdroje: Uživatel, který vlastní data a uděluje přístup.
- Klient: Aplikace požadující přístup k datům vlastníka zdroje. Může to být webová aplikace, mobilní aplikace nebo jakákoli jiná služba.
- Autorizační server: Ověřuje vlastníka zdroje a uděluje autorizaci klientovi.
- Server zdrojů: Hostuje chráněné zdroje a ověřuje přístupový token před udělením přístupu.
- Přístupový token: Přihlašovací údaj představující autorizaci udělenou vlastníkem zdroje klientovi.
- Obnovovací token: Dlouhodobý přihlašovací údaj používaný k získání nových přístupových tokenů, aniž by vlastník zdroje musel znovu autorizovat.
- Rozsahy (Scopes): Definuje konkrétní oprávnění, která klient požaduje.
Toky OAuth2: Volba správného přístupu
OAuth2 definuje několik autorizačních toků, z nichž každý je vhodný pro různé scénáře. Zde je přehled nejběžnějších toků a kdy je použít:
1. Tok heslem (Resource Owner Password Credentials)
Popis: Klient přímo získává přístupový token od autorizačního serveru poskytnutím uživatelského jména a hesla vlastníka zdroje. Případ použití: Vysoce důvěryhodné aplikace, jako jsou mobilní aplikace první strany. Měl by být používán pouze v případě, že jiné toky nejsou proveditelné. Výhody: Jednoduchá implementace. Nevýhody: Vyžaduje, aby klient zpracovával přihlašovací údaje vlastníka zdroje, což zvyšuje riziko jejich vyzrazení v případě kompromitace klienta. Méně bezpečné než jiné toky. Příklad: Vlastní mobilní aplikace společnosti přistupující k jejich internímu API.
Implementace ve FastAPI:
Nejprve nainstalujte potřebné balíčky:
pip install fastapi uvicorn python-multipart passlib[bcrypt] python-jose[cryptography]
Nyní si vytvoříme základní příklad:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Password hashing configuration
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"hashed_password": pwd_context.hash("password123"),
"scopes": ["read", "write"]
}
}
# Function to verify password
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# Function to create access token
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# OAuth2 endpoint for token generation
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = users.get(form_data.username)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"], "scopes": user["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
# Dependency to authenticate requests
async def get_current_user(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = users.get(username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Vysvětlení:
- Závislosti: Používáme `fastapi.security.OAuth2PasswordRequestForm` pro zpracování uživatelského jména a hesla.
- Hašování hesel: `passlib` se používá pro bezpečné hašování a ověřování hesel. Nikdy neukládejte hesla v prostém textu!
- Generování JWT: `python-jose` se používá pro vytváření a ověřování JSON Web Tokenů (JWT).
- Endpoint `/token`: Tento endpoint zpracovává proces přihlášení. Ověřuje uživatelské jméno a heslo, a pokud jsou platné, generuje přístupový token.
- Závislost `get_current_user`: Tato funkce ověřuje přístupový token a načítá uživatele.
- Endpoint `/users/me`: Toto je chráněný endpoint, který pro přístup vyžaduje platný přístupový token.
2. Implicitní tok
Popis: Klient přímo obdrží přístupový token od autorizačního serveru poté, co se vlastník zdroje ověří. Přístupový token je vrácen ve fragmentu URL. Případ použití: Jednostránkové aplikace (SPA) a jiné prohlížečové aplikace, kde není možné ukládat klientské tajemství. Výhody: Jednoduché pro prohlížečové aplikace. Nevýhody: Méně bezpečné než jiné toky, protože přístupový token je vystaven v URL. Není vydán obnovovací token. Příklad: JavaScriptová aplikace přistupující k API sociálních médií.
Úvahy o implementaci ve FastAPI:
Přestože FastAPI přímo nezpracovává frontendové aspekty Implicitního toku (protože se jedná primárně o backendový framework), pro správu ověřovacího toku byste použili frontendový framework jako React, Vue nebo Angular. FastAPI by primárně fungovalo jako Resource Server.
Zjednodušený backend (FastAPI - Resource Server) Příklad:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2AuthorizationCodeBearer
from jose import JWTError, jwt
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"scopes": ["read", "write"]
}
}
# OAuth2 scheme - using AuthorizationCodeBearer for token verification
oauth2_scheme = OAuth2AuthorizationCodeBearer(authorizationUrl="/auth", tokenUrl="/token") # These URLs are handled by the Authorization Server (not this FastAPI app).
# Dependency to authenticate requests
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = users.get(username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Klíčové body pro implicitní tok s FastAPI:
- Role autorizačního serveru: Skutečná autorizace a vydávání tokenů probíhá na samostatném autorizačním serveru. FastAPI funguje jako Resource Server a ověřuje token.
- Zpracování frontendem: Frontendová aplikace (např. React, Vue) zpracovává přesměrování na autorizační server, přihlášení uživatele a získání přístupového tokenu z fragmentu URL.
- Bezpečnostní aspekty: Vzhledem k vystavení přístupového tokenu v URL je klíčové používat HTTPS a udržovat krátkou životnost tokenu. Implicitnímu toku by se mělo pokud možno vyhnout ve prospěch toku autorizačního kódu s PKCE.
3. Tok autorizačního kódu
Popis: Klient nejprve získá autorizační kód od autorizačního serveru, který pak vymění za přístupový token. Tento tok zahrnuje přesměrování z klienta na autorizační server a zpět. Případ použití: Webové aplikace a mobilní aplikace, kde lze bezpečně uložit klientské tajemství. Výhody: Bezpečnější než implicitní tok, protože přístupový token není přímo vystaven v prohlížeči. Nevýhody: Složitější na implementaci než implicitní tok. Příklad: Aplikace třetí strany požadující přístup k datům uživatele na Google Drive.
Tok autorizačního kódu s PKCE (Proof Key for Code Exchange):
PKCE je rozšíření toku autorizačního kódu, které snižuje riziko zachycení autorizačního kódu. Je vysoce doporučené pro mobilní aplikace a SPA, protože nevyžaduje, aby klient ukládal tajemství.
Úvahy o implementaci ve FastAPI: Podobně jako u implicitního toku by FastAPI v tomto toku primárně fungovalo jako Resource Server. Za ověřování a vydávání autorizačních kódů je odpovědný samostatný autorizační server.
Zjednodušený backend (FastAPI - Resource Server) Příklad (podobný implicitnímu toku):
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2AuthorizationCodeBearer
from jose import JWTError, jwt
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"scopes": ["read", "write"]
}
}
# OAuth2 scheme - using AuthorizationCodeBearer for token verification
oauth2_scheme = OAuth2AuthorizationCodeBearer(authorizationUrl="/auth", tokenUrl="/token") # These URLs are handled by the Authorization Server.
# Dependency to authenticate requests
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = users.get(username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Klíčové body pro tok autorizačního kódu s PKCE a FastAPI:
- Role autorizačního serveru: Autorizační server zpracovává generování autorizačního kódu, ověřování ověřovače kódu PKCE a vydávání přístupového tokenu.
- Zpracování frontendem: Frontendová aplikace generuje ověřovač kódu a výzvu kódu, přesměruje uživatele na autorizační server, obdrží autorizační kód a vymění ho za přístupový token.
- Zvýšená bezpečnost: PKCE zabraňuje útokům zachycení autorizačního kódu, díky čemuž je vhodný pro SPA a mobilní aplikace.
- Doporučený přístup: Tok autorizačního kódu s PKCE je obecně nejbezpečnější a doporučený tok pro moderní webové a mobilní aplikace.
4. Tok klientských pověření
Popis: Klient se přímo ověřuje u autorizačního serveru pomocí vlastních pověření (ID klienta a klientské tajemství) k získání přístupového tokenu. Případ použití: Komunikace mezi stroji, jako jsou backendové služby přistupující jedna k druhé. Výhody: Jednoduché pro backendové služby. Nevýhody: Není vhodné pro ověřování uživatelů. Příklad: Služba zpracování dat přistupující k databázové službě.
Implementace ve FastAPI:
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from jose import JWTError, jwt
from datetime import datetime, timedelta
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Dummy client database (replace with a real database in production)
clients = {
"client_id": {
"client_secret": "client_secret",
"scopes": ["read", "write"]
}
}
# Function to create access token
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# HTTP Basic Authentication scheme
security = HTTPBasic()
# Endpoint for token generation
@app.post("/token")
async def login(credentials: HTTPBasicCredentials = Depends(security)):
client = clients.get(credentials.username)
if not client:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect client ID or secret",
headers={"WWW-Authenticate": "Basic"},
)
if credentials.password != client["client_secret"]:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect client ID or secret",
headers={"WWW-Authenticate": "Basic"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": credentials.username, "scopes": client["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
# Dependency to authenticate requests
async def get_current_client(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
client_id: str = payload.get("sub")
if client_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
client = clients.get(client_id)
if client is None:
raise credentials_exception
return client
async def get_current_active_client(current_client = Depends(get_current_client)):
return current_client
# Example protected endpoint
@app.get("/data")
async def read_data(current_client = Depends(get_current_active_client)):
return {"message": "Data accessed by client: " + current_client["client_secret"]}
Vysvětlení:
- HTTP Basic Authentication: Používáme `fastapi.security.HTTPBasic` pro ověření klienta.
- Endpoint `/token`: Tento endpoint zpracovává ověření klienta. Ověřuje ID klienta a tajemství, a pokud jsou platné, generuje přístupový token.
- Závislost `get_current_client`: Tato funkce ověřuje přístupový token a načítá klienta.
- Endpoint `/data`: Toto je chráněný endpoint, který pro přístup vyžaduje platný přístupový token.
Obnova tokenu
Přístupové tokeny mají obvykle krátkou životnost, aby se minimalizoval dopad kompromitovaných tokenů. Obnovovací tokeny jsou dlouhodobé přihlašovací údaje, které lze použít k získání nových přístupových tokenů, aniž by uživatel musel znovu autorizovat.
Úvahy o implementaci:
- Ukládání obnovovacích tokenů: Obnovovací tokeny by měly být ukládány bezpečně, ideálně zašifrované v databázi.
- Endpoint pro obnovu tokenu: Vytvořte vyhrazený endpoint (např. `/refresh_token`) pro zpracování požadavků na obnovovací tokeny.
- Odvolávání obnovovacích tokenů: Implementujte mechanismus pro odvolání obnovovacích tokenů, pokud byly kompromitovány nebo již nejsou potřeba.
Příklad (rozšíření příkladu toku heslem):
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
import secrets # For generating secure random strings
app = FastAPI()
# Replace with a strong, randomly generated secret key
SECRET_KEY = "YOUR_SECRET_KEY"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_DAYS = 30 # Longer lifetime for refresh tokens
# Password hashing configuration
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Dummy user database (replace with a real database in production)
users = {
"johndoe": {
"username": "johndoe",
"hashed_password": pwd_context.hash("password123"),
"scopes": ["read", "write"],
"refresh_token": None # Store refresh token here
}
}
# Function to verify password (same as before)
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
# Function to create access token (same as before)
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# Function to create refresh token
def create_refresh_token():
return secrets.token_urlsafe(32) # Generate a secure random string
# OAuth2 endpoint for token generation
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = users.get(form_data.username)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not verify_password(form_data.password, user["hashed_password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Create refresh token and store it (securely in a database in real-world)
refresh_token = create_refresh_token()
user["refresh_token"] = refresh_token # Store it in the user object for now (INSECURE for production)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"], "scopes": user["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer", "refresh_token": refresh_token}
# Endpoint for refreshing the access token
@app.post("/refresh_token")
async def refresh_access_token(refresh_token: str):
# Find user by refresh token (securely query the database)
user = next((user for user in users.values() if user["refresh_token"] == refresh_token), None)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token",
headers={"WWW-Authenticate": "Bearer"},
)
# Create a new access token
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user["username"], "scopes": user["scopes"]},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
# Dependency to authenticate requests (same as before)
async def get_current_user(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = next((user for user in users.values() if user["username"] == username), None)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user = Depends(get_current_user)):
return current_user
# Example protected endpoint (same as before)
@app.get("/users/me")
async def read_users_me(current_user = Depends(get_current_active_user)):
return {"username": current_user["username"], "scopes": current_user["scopes"]}
Důležité bezpečnostní poznámky:
- Ukládání obnovovacích tokenů: Příklad ukládá obnovovací token do paměti (nebezpečně). V produkčním prostředí ukládejte obnovovací tokeny bezpečně do databáze, nejlépe zašifrované.
- Rotace obnovovacích tokenů: Zvažte implementaci rotace obnovovacích tokenů. Po použití obnovovacího tokenu vygenerujte nový obnovovací token a zneplatněte starý. Tím se omezí dopad kompromitovaných obnovovacích tokenů.
- Auditování: Zaznamenávejte použití obnovovacích tokenů pro detekci podezřelých aktivit.
Osvědčené bezpečnostní postupy
Implementace OAuth2 je pouze prvním krokem. Dodržování osvědčených bezpečnostních postupů je klíčové pro ochranu vašeho API a uživatelských dat.
- Používejte HTTPS: Vždy používejte HTTPS k šifrování komunikace mezi klientem, autorizačním serverem a serverem zdrojů.
- Ověřujte vstup: Důkladně ověřujte všechna vstupní data, abyste předešli injekčním útokům.
- Omezení rychlosti (Rate Limiting): Implementujte omezení rychlosti, abyste předešli útokům hrubou silou.
- Pravidelně aktualizujte závislosti: Udržujte svůj framework FastAPI a všechny závislosti aktuální, abyste opravili bezpečnostní zranitelnosti.
- Používejte silná tajemství: Generujte silná, náhodná tajemství pro svá klientská tajemství a podpisové klíče JWT. Ukládejte tato tajemství bezpečně (např. pomocí proměnných prostředí nebo systému pro správu tajemství).
- Monitorujte a logujte: Monitorujte své API na podezřelé aktivity a logujte všechny události ověřování a autorizace.
- Vymáhejte nejmenší privilegia: Udělujte klientům pouze nezbytná oprávnění (rozsahy).
- Správné zpracování chyb: Vyhněte se odhalování citlivých informací v chybových zprávách.
- Zvažte použití dobře prověřené knihovny OAuth2: Místo implementace OAuth2 od základů zvažte použití dobře prověřené knihovny jako Authlib. Authlib poskytuje robustnější a bezpečnější implementaci OAuth2.
Za hranice základů: Pokročilé úvahy
Jakmile máte základní implementaci OAuth2, zvažte tato pokročilá témata:
- Správa souhlasu: Poskytněte uživatelům jasnou a granulární kontrolu nad oprávněními, která udělují klientům.
- Delegovaná autorizace: Implementujte podporu pro delegovanou autorizaci, která uživatelům umožňuje autorizovat klienty, aby jednali jejich jménem.
- Vícefaktorové ověřování (MFA): Integrujte MFA pro zvýšení bezpečnosti.
- Federovaná identita: Podporujte ověřování prostřednictvím poskytovatelů identity třetích stran (např. Google, Facebook, Twitter).
- Dynamická registrace klienta: Umožněte klientům dynamicky se registrovat u vašeho autorizačního serveru.
Závěr
Implementace ověřování OAuth2 s FastAPI je silným způsobem, jak zabezpečit vaše API a chránit uživatelská data. Pochopením různých toků OAuth2, implementací osvědčených bezpečnostních postupů a zvážením pokročilých témat můžete vytvořit robustní a bezpečné API, které splňuje potřeby vašich uživatelů a aplikací. Nezapomeňte zvolit vhodný tok pro váš konkrétní případ použití, upřednostnit bezpečnost a neustále monitorovat a vylepšovat svůj ověřovací systém. Zatímco uvedené příklady ukazují základní principy, vždy je přizpůsobte svým specifickým požadavkům a konzultujte s bezpečnostními experty pro důkladnou kontrolu.